其實因為前面的彩格範例是突然想到的,所以今天才開始弄接下來主題想要做到的內容。今天是畫天空的顏色。
common.hlsl
// 根據 Vertex ID 取得 Fullscreen Triangle 的頂點位置
float2 GetFullscreenTrianglePosition(uint vertexId)
{
// 定義覆蓋整個畫面的三角形三個頂點
static const float2 positions[3] =
{
float2(-1.0f, -1.0f),
float2(-1.0f, 3.0f),
float2(3.0f, -1.0f)
};
// 回傳目前 Vertex ID 對應的位置
return positions[vertexId];
}
sky_gradient.hlsl
#include "common.hlsl"
// 定義 Vertex Shader 傳給 Pixel Shader 的資料
struct VSOutput
{
// 儲存頂點的裁切空間位置
float4 position : SV_POSITION;
// 儲存垂直方向的天空漸層值
float gradient : TEXCOORD0;
};
// 根據 Vertex ID 建立 Fullscreen Triangle 的頂點資料
VSOutput VSMain(uint vertexId : SV_VertexID)
{
// 取得目前 Vertex ID 對應的 Fullscreen Triangle 位置
const float2 position = GetFullscreenTrianglePosition(vertexId);
// 建立 Vertex Shader 輸出資料
VSOutput output;
// 將二維位置轉換成裁切空間座標
output.position = float4(position, 0.0f, 1.0f);
// 將 Y 座標從 [-1, 1] 映射到 [0, 1] 作為天空漸層值
output.gradient = position.y * 0.5f + 0.5f;
// 回傳 Vertex Shader 輸出
return output;
}
// 根據垂直漸層產生天空顏色
float4 PSMain(VSOutput input) : SV_TARGET
{
// 定義接近地平線的天空顏色
const float3 horizonColor = float3(0.65f, 0.85f, 1.0f);
// 定義接近天頂的天空顏色
const float3 zenithColor = float3(0.08f, 0.35f, 0.85f);
// 將插值後的漸層值限制在 [0, 1] 範圍
const float gradient = saturate(input.gradient);
// 根據漸層值在地平線與天頂顏色之間進行線性插值
const float3 color = lerp(horizonColor, zenithColor, gradient);
// 輸出不透明的天空顏色
return float4(color, 1.0f);
}
main.cpp
#include <cstdlib>
#include <exception>
#include <stdexcept>
#include <directx/d3dx12_core.h>
#include "graphics_engine.h"
#include "my_engine.h"
#include "skyline_debugger.h"
#include "system.h"
namespace
{
void initRootSignature(RootSignature& rs)
{
rs.init(D3D12_FILTER_MIN_MAG_MIP_LINEAR,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP,
D3D12_TEXTURE_ADDRESS_MODE_WRAP);
}
D3D12_GRAPHICS_PIPELINE_STATE_DESC createSkyPipelineStateDescription(
ID3D12RootSignature* rootSignature,
ID3DBlob* vertexShader,
ID3DBlob* pixelShader)
{
if (rootSignature == nullptr)
throw std::invalid_argument("SkyPipeline: Root signature is required.");
if (vertexShader == nullptr)
throw std::invalid_argument("SkyPipeline: Vertex shader is required.");
if (pixelShader == nullptr)
throw std::invalid_argument("SkyPipeline: Pixel shader is required.");
D3D12_GRAPHICS_PIPELINE_STATE_DESC description{};
description.InputLayout = {nullptr, 0};
description.pRootSignature = rootSignature;
// 把 ID3DBlob 裡的 vertex shader 跟 pixel shader 放進 description
description.VS = CD3DX12_SHADER_BYTECODE(vertexShader);
description.PS = CD3DX12_SHADER_BYTECODE(pixelShader);
description.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
description.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
description.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
description.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
description.DepthStencilState.DepthEnable = FALSE;
description.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
description.DepthStencilState.StencilEnable = FALSE;
description.SampleMask = UINT_MAX;
description.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
description.NumRenderTargets = 1;
description.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
description.DSVFormat = DXGI_FORMAT_D32_FLOAT;
description.SampleDesc.Count = 1;
return description;
}
void initPipelineState(PipelineState& pipelineState, RootSignature& rs, Shader& vs, Shader& ps)
{
const D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc =
createSkyPipelineStateDescription(rs.get(), vs.getCompiledBlob(), ps.getCompiledBlob());
pipelineState.init(psoDesc);
}
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)
{
try
{
SkylineDebugger::Initialize("DayX_Sky");
// 基礎圖形初始化
SKYLINE_LOG(INFO) << "[DayX_Sky] Initializing window...";
initWindow(hInstance, hPrevInstance, lpCmdLine, nCmdShow, TEXT("Game"));
if (g_hWnd == nullptr)
throw std::runtime_error("DayX_Sky: Failed to create the application window.");
SKYLINE_LOG(INFO) << "[DayX_Sky] Initializing graphics engine...";
GraphicsEngine graphicsEngine;
graphicsEngine.init(g_hWnd, FRAME_BUFFER_W, FRAME_BUFFER_H);
SkylineDebugger::ConfigureD3D12(graphicsEngine.getD3DDevice());
// 1. RootSignature 初始化
SKYLINE_LOG(INFO) << "[DayX_Sky] Initializing root signature...";
RootSignature rootSignature;
initRootSignature(rootSignature);
SKYLINE_LOG(INFO) << "[DayX_Sky] Compiling shaders...";
Shader vs, ps;
vs.loadVS("assets/shaders/sky_gradient.hlsl", "VSMain");
ps.loadPS("assets/shaders/sky_gradient.hlsl", "PSMain");
SKYLINE_LOG(INFO) << "[DayX_Sky] Creating pipeline state...";
PipelineState pipelineState;
initPipelineState(pipelineState, rootSignature, vs, ps);
SKYLINE_LOG(INFO) << "[DayX_Sky] Initialization completed.";
RenderContext& renderContext = graphicsEngine.getRenderContext();
bool isFirstFrame = true;
while (dispatchWindowMessage())
{
if (isFirstFrame)
SKYLINE_LOG(INFO) << "[DayX_Sky] Rendering first frame...";
graphicsEngine.beginRender();
renderContext.setRootSignature(rootSignature);
renderContext.setPipelineState(pipelineState);
renderContext.setPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
renderContext.draw(3);
graphicsEngine.endRender();
SkylineDebugger::LogD3D12Messages(graphicsEngine.getD3DDevice());
if (isFirstFrame)
{
SKYLINE_LOG(INFO) << "[DayX_Sky] First frame presented.";
isFirstFrame = false;
}
}
SkylineDebugger::Shutdown();
return EXIT_SUCCESS;
}
catch (const std::exception& exception)
{
SkylineDebugger::ShowFatalError("DayX_Sky initialization failed", exception);
SkylineDebugger::Shutdown();
return EXIT_FAILURE;
}
catch (...)
{
SkylineDebugger::ShowFatalError("DayX_Sky initialization failed", "An unknown fatal error occurred.");
SkylineDebugger::Shutdown();
return EXIT_FAILURE;
}
}
